File.createDir("./plugins/UpdateEX/Download")

function httpGet(url) {
    return new Promise((rev, rej) => {
        network.httpGet(url, (status, result) => {
            rev({ status, result })
        });
        setTimeout(() => {
            rev({ status: 502, result: {} });
        }, 5000);
    })
}

let updateList = {};

const configAPI = {
    data: {
        common: {
            language: "zh-cn"
        },
        autoUpdate: false,
        whitelist: []
    },
    save() {
        file.writeTo("./plugins/UpdateEX/config.json", JSON.stringify(this.data));
    },
    reload() {
        this.data = JSON.parse(File.readFrom("./plugins/UpdateEX/config.json"));
    }
};
// 读取已有的配置文件
if (file.exists("./plugins/UpdateEX/config.json")) {
    configAPI.reload();
} else {
    configAPI.save();
}

const i18n = { /**
     * 翻译类型
     * @type {object}
     */
    data: {},
    /**
     * 翻译主代码
     * @param {string} sentence 翻译字段
     * @param {Array[string]} replacer 替换字符
     * @returns {string}
     */
    $t(sentence, replacer = []) {
        if (!this.data[sentence]) {
            return `Translate Error:${sentence}`;
        }
        let output = this.data[sentence];
        for (let index = 0; index < replacer.length; index++) {
            const element = replacer[index];
            output = output.replace("%s" + index, element);
        }
        return output;
    },
    reload() {
        if (file.exists("./plugins/UpdateEX/i18n/" + configAPI.data.common.language + ".json")) {
            i18n.data = data.parseJson(File.readFrom("./plugins/UpdateEX/i18n/" + configAPI.data.common.language + ".json"));
        }
    }
};
if (file.exists("./plugins/UpdateEX/i18n/" + configAPI.data.common.language + ".json")) {
    i18n.data = data.parseJson(File.readFrom("./plugins/UpdateEX/i18n/" + configAPI.data.common.language + ".json"));
}

async function checkUpdate(player = null) { // 定义俩正则
    const filePatt = /.(js|lua)$/
    const infoPatt = /^(\/\/|--)UpgradeEX\*(.*?)\*\//
    /**
     * 文件列表，包含文件夹
     * @type {string[]}
     */
    let fileList = File.getFilesList("./plugins");
    for (const fileName of fileList) {
        if (!filePatt.test(fileName)) { // 文件不是js/lua
            continue;
        }
        if (File.checkIsDir("./plugins/" + fileName)) { // 是文件夹，跳过
            continue;
        }
        if (configAPI.data.whitelist.includes(fileName)) {
            continue;
        }
        // 打开文件读取
        let fileItf = new File("./plugins/" + fileName, file.ReadMode);
        // 读取第一行信息
        let updateEXinfo = fileItf.readLineSync();
        fileItf.close();
        // 不支持upgradeEX
        if (!infoPatt.test(updateEXinfo)) {
            continue;
        }
        // 解码头
        let plugInfo = infoPatt.exec(updateEXinfo);
        if (plugInfo.length !== 3) {
            if (player) {
                player.tell(i18n.$t("check.err1", [fileName]));
            } else {
                logger.error(`${fileName} updateHeader can't be parsed，please UPDATE manually (ERR.1)`);
            }
            continue;
        }
        try {
            plugInfo = JSON.parse(plugInfo[2]);
        } catch (error) {
            if (player) {
                player.tell(i18n.$t("check.err2", [fileName]));
            } else {
                logger.error(`${fileName} updateHeader can't be parsed，please UPDATE manually (ERR.2)`);
            }
            continue;
        }
        // 解码成功
        if (!plugInfo.name) {
            if (player) {
                player.tell(i18n.$t("check.err3", [fileName, '"name"']));
            } else {
                logger.error(`${fileName} Lost 'name' key (ERR.3)`);
            }
            continue;
        } else if (!plugInfo.platform) {
            if (player) {
                player.tell(i18n.$t("check.err3", [fileName, '"platform"']));
            } else {
                logger.error(`${fileName} Lost 'platform' key (ERR.4)`);
            }
            continue;
        } else if (!plugInfo.repo) {
            if (player) {
                player.tell(i18n.$t("check.err3", [fileName, '"repo"']));
            } else {
                logger.error(`${fileName} Lost 'repo' key (ERR.5)`);
            }
            continue;
        } else if (!plugInfo.currentRelease) {
            if (player) {
                player.tell(i18n.$t("check.err3", [fileName, '"currentRelease"']));
            } else {
                logger.error(`${fileName} Lost 'currentRelease' key (ERR.6)`);
            }
            continue;
        };
        let releaseInfo;
        if (player) {
            player.tell(i18n.$t("check.startCheck", [fileName]));
        } else {
            logger.info(`Checking update for ${fileName} ...`);
        }
        if (plugInfo.platform === "gitee") {
            releaseInfo = await httpGet(`https://gitee.com/api/v5/repos/${plugInfo.repo
                }/releases`);
        } else if (plugInfo.platform === "github") {
            releaseInfo = await httpGet(`https://api.github.com/repos/${plugInfo.repo
                }/releases`);
        } else {
            logger.error(`${fileName} Platform not supported (ERR.7)`);
            continue;
        }
        if (!releaseInfo.status === 502) {
            if (player) {
                player.tell(i18n.$t("check.notReach"));
            } else {
                logger.warn(`${fileName}'s repo can't be reach,maybe you can try again later`);
            }
            continue;
        }
        releaseArray = JSON.parse(releaseInfo.result);
        if (typeof releaseArray === "object" && releaseArray.message) {
            if (player) {
                player.tell(i18n.$t("check.remoteError", [fileName, releaseArray.message]));
            } else {
                logger.error(`${fileName}'s release has an error：${releaseArray.message
                    }`);
            }
            continue;
        }
        if (releaseArray.length === 0) {
            if (player) {
                player.tell(i18n.$t("check.emptyRelease", [fileName]));
            } else {
                logger.error(`${fileName}' doesn't have any releases`);
            }
            continue;
        }
        // 获取最新的release
        if (plugInfo.platform === "gitee") {
            releaseInfo = releaseArray.pop();
            if (!releaseInfo.assets.length === 0) {
                if (player) {
                    player.tell(i18n.$t("check.emptyPayload", [fileName]));
                } else {
                    logger.error(`${fileName}' release doesn't have any Payload`);
                }
                continue;
            }
            // 上一个更新，用于判断是否需要全量包。
            lastInfo = releaseArray.pop();
            let useminPack = !!(lastInfo && lastInfo.tag_name === plugInfo.currentRelease);
            updateList[plugInfo.name] = {
                file: fileName,
                useminPack,
                avatar: releaseInfo.author.avatar_url,
                currentTag: plugInfo.currentRelease,
                remoteTag: releaseInfo.tag_name,
                body: releaseInfo.body,
                assets: releaseInfo.assets
            }
        } else if (plugInfo.platform === "github") {
            releaseInfo = releaseArray.shift()
            if (!releaseInfo.assets.length === 0) {
                logger.error(`${fileName}' release doesn't have any Payload`);
                continue;
            }
            lastInfo = releaseArray.shift();
            let useminPack = !!(lastInfo && lastInfo.tag_name === plugInfo.currentRelease);
            updateList[plugInfo.name] = {
                file: fileName,
                useminPack,
                avatar: releaseInfo.author.avatar_url,
                currentTag: plugInfo.currentRelease,
                remoteTag: releaseInfo.tag_name,
                body: releaseInfo.body,
                assets: releaseInfo.assets
            }
        }
        if (plugInfo.currentRelease === releaseInfo.tag_name) { // 最新版本，无需更新
            if (player) {
                player.tell(i18n.$t("check.latest", [fileName, releaseInfo.tag_name]));
            } else {
                logger.info(`${fileName} is latest : ${releaseInfo.tag_name
                    }`);
            }
            continue;
        };
        if (player) {
            player.tell(i18n.$t("check.hasUpdate", [fileName, plugInfo.currentRelease, releaseInfo.tag_name]));
        } else {
            logger.warn(`${fileName} has a update:(local: ${plugInfo.currentRelease
                } remote: ${releaseInfo.tag_name
                })`);
            logger.info("\n==================================================\n" + releaseInfo.body + `\n==================================================\n\u001b[46;37mCommand 【update update ${plugInfo.name
                }】 to update it\u001b[0m`);
        }
    }
};

function download(url, file) {
    return new Promise((rev, rej) => {
        system.newProcess(`./plugins/UpdateEX/wget.exe ${url} -O ${file} `, (exitcode, output) => {
            rev({ exitcode, output })
        })
    })
};

function uncompress() {
    return new Promise((rev, rej) => {
        system.newProcess(`./plugins/UpdateEX/7za.exe x  ./plugins/UpdateEX/Download/plugin.zip -r -oplugins/UpdateEX/Download`, (exitcode, output) => {
            rev({ exitcode, output })
        })
    })
};

function uncompressToPlug() {
    return new Promise((rev, rej) => {
        system.newProcess(`./plugins/UpdateEX/7za.exe x  ./plugins/UpdateEX/Download/plugin.zip -y -r -oplugins`, (exitcode, output) => {
            rev({ exitcode, output })
        })
    })
};

mc.listen("onServerStarted", async () => {
    let comm = mc.newCommand("update", i18n.$t("common.command"), PermType.GameMasters);
    comm.setEnum("GuiEnum", ['gui']);
    comm.setEnum("UpdateEnum", ['update']);
    comm.setEnum("CheckEnum", ['check']);
    comm.setEnum("UpdateAllEnum", ['update_all']);
    comm.mandatory("action", ParamType.Enum, "UpdateAllEnum", 1);
    comm.mandatory("action", ParamType.Enum, "UpdateEnum", 1);
    comm.mandatory("action", ParamType.Enum, "GuiEnum", 1);
    comm.mandatory("action", ParamType.Enum, "CheckEnum", 1);
    comm.mandatory("name", ParamType.String);
    comm.overload(["UpdateEnum", "name"]);
    comm.overload(["UpdateAllEnum"]);
    comm.overload(["GuiEnum"]);
    comm.overload(["CheckEnum"]);
    comm.setCallback((_cmd, _ori, out, res) => {
        switch (res.action) {
            case "update":
                return CommandUpdateHander(_cmd, _ori, out, res);
            case "gui":
                if (!_ori.player) {
                    return out.error(i18n.$t("command.notPlayer"));
                }
                PlayerGuiHander(_ori.player)
                break;
            case "check": updateList = {};
                checkUpdate(_ori.player);
                return out.success(i18n.$t("check.start"));
                break;
            case "update_all": UpdateAll(_ori.player);
                return out.success(i18n.$t("updateall.start"));
        }
    });
    comm.setup();
    logger.info("Checking plugins update...");
    await checkUpdate();
    logger.info(`${Object.keys(updateList).length
        } plugin can be update.`);
});

function CommandUpdateHander(cmd, ori, out, res) {
    if (typeof updateList[res.name] === "undefined") {
        return out.error(i18n.$t("command.undefined"));
    }
    if (!(ori.type === 0 || ori.type === 7)) {
        return out.error(i18n.$t("command.typeNotSupport"));
    }
    Update(ori.player, res.name);
}
async function Update(player, name) {
    if (player) {
        player.tell(i18n.$t("update.clear", [name]));
    } else {
        logger.log(i18n.$t("update.clear", [name]))
    }
    // 清理残渣
    File.delete("./plugins/UpdateEX/Download/");
    File.mkdir("./plugins/UpdateEX/Download/");
    // 找网址
    let url;
    if (updateList[name].useminPack) { // 使用最小量包
        for (const asset of updateList[name].assets) {
            if (asset.name && asset.name.includes("OTA-MIN")) { // 有指定的ota-min包，使用。
                url = asset.browser_download_url;
                break;
            }
        }
    }
    for (const asset of updateList[name].assets) {
        if (asset.name && asset.name.includes("OTA")) { // 有指定的ota包，使用。
            url = asset.browser_download_url;
            break;
        }
    }
    if (!url) { // 没有指定的OTA
        url = updateList[name].assets[0].browser_download_url;
    }
    if (player) {
        player.tell(i18n.$t("update.download", [
            name, updateList[name].remoteTag
        ]));
    } else {
        logger.log(i18n.$t("update.download", [
            name, updateList[name].remoteTag
        ]))
    }
    await download(url, "./plugins/UpdateEX/Download/plugin.zip");
    if (player) {
        player.tell(i18n.$t("update.uncompress", []));
    } else {
        logger.log(i18n.$t("update.uncompress", []))
    }
    await uncompress();
    if (player) {
        player.tell(i18n.$t("update.install", []));
    } else {
        logger.log(i18n.$t("update.install", []))
    }
    if (File.exists("./plugins/UpdateEX/Download/install.js")) {
        ll.require("./plugins/UpdateEX/Download/install.js");
    } else {
        await uncompressToPlug();
        mc.runcmdEx(`ll unload "${updateList[name].file}"`);
        setTimeout(() => {
            mc.runcmdEx(`ll load "./plugins/${updateList[name].file}"`);
            if (player) {
                player.tell(i18n.$t("update.finish", [
                    name, updateList[name].remoteTag
                ]));
            } else {
                logger.log(i18n.$t("update.finish", [
                    name, updateList[name].remoteTag
                ]))
            }
        }, 500);
    }
    File.delete("./plugins/UpdateEX/Download/");
    updateList[name].currentTag = updateList[name].remoteTag;
};
async function UpdateAll(player) {
    for (const item of Object.keys(updateList)) {
        if (updateList[item].currentTag === updateList[item].remoteTag) {
            continue;
        }
        await Update(player, item);
    }
}

function PlayerGuiHander(player) {
    let fm = mc.newSimpleForm().setTitle(i18n.$t("gui.main.title"));
    for (const key in updateList) {
        const element = updateList[key];
        if (element.currentTag === element.remoteTag) {
            fm.addButton(i18n.$t("gui.main.itemhasUpdate", [key, element.currentTag]), element.avatar);
        } else {
            fm.addButton(i18n.$t("gui.main.itemtoUpdate", [key, element.currentTag, element.remoteTag]), element.avatar);
        }
    }
    player.sendForm(fm, (pl, dt) => {
        if (dt == null) {
            return;
        };
        let key = Object.keys(updateList)[dt];
        let data = updateList[key];
        pl.sendModalForm(key, i18n.$t("gui.confirm.content", [data.currentTag, data.remoteTag, data.body]), i18n.$t("gui.startUpdate"), i18n.$t("gui.cancel"), (plm, dtm) => {
            if (dtm) {
                Update(plm, key);
            } else {
                PlayerGuiHander(plm);
            }
        })
    })

}
